<?php

namespace App\Core\Database;

use App\Core\Exceptions\UnsupportedCacheTypeException;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Query\Grammars\Grammar;
use Illuminate\Database\Query\Processors\Processor;
use App\Core\Exceptions\QueryConditionsNotFoundException;

class QueryBuilder extends Builder
{
    /**
     * The model being queried.
     *
     * @var \Illuminate\Database\Eloquent\Model
     */
    public $model;

    /**
     * Indicates whether open the debug mode.
     *
     * @var boolean
     */
    protected $debug = false;

    /**
     * The columns that should be returned.
     *
     * @var array
     */
    public $original;

    /**
     * @var CacherManager
     */
    public $cacher;

    /**
     * Create a new query builder instance.
     *
     * @param QueryCache $cache
     * @param ConnectionInterface $connection
     * @param Grammar $grammar
     * @param Processor $processor
     */
    public function __construct(ConnectionInterface $connection, Grammar $grammar, Processor $processor, Model $model)
    {
        $this->model = $model;

        $this->cacher = new CacherManager($this);

        parent::__construct($connection, $grammar, $processor);
    }

    /**
     * Get a new instance of the query builder.
     *
     * @return \Illuminate\Database\Query\Builder
     */
    public function newQuery()
    {
        return new static($this->connection, $this->grammar, $this->processor, $this->model);
    }

    /**
     * Set a model instance for the model being queried.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return $this
     */
    protected function setModel($model)
    {
        $this->model = $model;

        return $this;
    }

    /**
     * Set the debug mode
     *
     * @param bool $debug
     * @return $this
     */
    public function setDebug($debug = false)
    {
        $this->debug = $debug;

        return $this;
    }

    public function setExpire($seconds = 3600)
    {
        $this->cacher->setExpire($seconds);
    }

    public function setPrefix($prefix = '')
    {
        $this->cacher->setPrefix($prefix);
    }

    /**
     * Rewrite the runSelect method for using model cache
     *
     * @return array
     */
    public function runSelect()
    {
        // 是否禁用模型查询缓存
        if (! Cacher::isEnable() || (! $this->runSqlMode() &&  ! $this->usesCache()) ) {
            return parent::runSelect();
        }

        return $this->runQuery();
    }

    /**
     * Determine if the model uses cache.
     *
     * @return bool
     */
    public function usesCache()
    {
        return $this->model->isCacheable();
    }

    /**
     * 不使用缓存
     *
     * @return bool
     */
    public function cantCache()
    {
        if (
            ! empty($this->joins) ||
            ! empty($this->aggregate) ||
            ! empty($this->distinct) ||
            ! empty($this->groups) ||
            ! empty($this->unions) ||
            $this->isOnlySelectPrimaryKey()
        ) {
            return true;
        }

        return false;
    }

    /**
     * 是否运行在sql模式下
     *
     * @return bool
     */
    protected function runSqlMode()
    {
        return $this->model->runInSqlMode();
    }

    /**
     * 只查询id的情况 不需要走缓存
     */
    protected function isOnlySelectPrimaryKey()
    {
        if (count($this->original) === 1) {
            if (strpos($this->original[0], '.') === false) {
                $column = $this->original[0];
            } else {
                $schema = explode('.', $this->original[0]);
                $column = end( $schema );
            }

            if ($this->getKeyName() == $column) {
                return true;
            }
        }

        return false;
    }

    public function runQuery()
    {
        // sql模式查询
        if ($this->runSqlMode()) {
            return $this->queryFromSqlCache();
        }

        return $this->queryFromIndexCache();
    }

    protected function queryFromSqlCache()
    {
        return $this->cacher->setDefaultCacher('sql')->cacheQuery(function () {
            return parent::runSelect();
        });
    }

    protected function queryFromIndexCache()
    {
        $this->recordColumns();

        // 过滤不执行缓存的查询类型
        if ($this->cantCache()) {
            return parent::runSelect();
        }

        $databaseIds = $this->queryPrimaryKeysFromDatabase();

        if (empty($databaseIds)) {
            return [];
        }

        return $this->queryFromCache($databaseIds);
    }

    /**
     * 记录当前查询的字段
     */
    protected function recordColumns()
    {
        $this->original = $this->columns;
    }

    /**
     * 过滤字段
     *
     * @param $results
     * @return array
     */
    protected function filterResultColumns($results)
    {
        print_r($this->original);

        if (is_null($this->original)) {
            return $results;
        }

        $filteredResult = [];
        foreach ($results as $rowObj) {
            $row = (array) $rowObj;
            $needDeleteKeys = array_diff(array_keys($row), $this->original);

            array_walk($needDeleteKeys, function ($key) use ($rowObj) {
                unset($rowObj->{$key});
            });

            $filteredResult[] = $rowObj;
        }

        return $filteredResult;
    }

    /**
     * 从数据库中查询所有的主键
     *
     * @return array
     */
    protected function queryPrimaryKeysFromDatabase()
    {
        $this->resetColumns();

        return parent::runSelect();
    }

    protected function resetColumns()
    {
        $this->columns = [$this->getFullKeyName()];

        return $this;
    }

    /**
     * 获取包含表名的主键名
     *
     * @return string
     */
    protected function getFullKeyName()
    {
        return $this->model->getTable() . '.' . $this->model->getKeyName();
    }

    protected function buildCacheKeys($databaseIds)
    {
        $allIds = [];
        $neededCacheKeys = [];
        foreach ($databaseIds as $idObj) {
            $id = $this->getPrimaryKeyValue($idObj);
            if (is_null($id)) {
                continue;
            }
            $allIds[] = $id;
            $neededCacheKeys[] = $this->getCacheKey($id);
        }

        return [$allIds, $neededCacheKeys];
    }

    /**
     * 从缓存中查询结果
     *
     * @param $databaseIds
     * @return mixed
     */
    protected function queryFromCache($databaseIds)
    {
        [$allIds, $neededCacheKeys]= $this->buildCacheKeys($databaseIds);

        if (count($neededCacheKeys) == 0) {
            return [];
        }

        // 从redis中查询数据
        $cacheResult = $this->cacher->get($neededCacheKeys, $this->original);

        if ($this->debug) {
            echo 'redis的查询结果: ' . PHP_EOL;
            print_r($cacheResult);
        }

        // 解析redis的返回结果，如果结果在redis当中也不存在 需要从表中查询 再写入redis
        $parsedResult = $emptyResultIds = $hasResultIds = [];
        foreach ($cacheResult as $row) {
            if (is_null($row)) {
                continue;
            }

            $parsedRow = $this->parseCacheResultRow($row);
            $parsedResult[] = $parsedRow;
            $hasResultIds[] = $parsedRow->{$this->getKeyName()};
        }

        // redis中没有缓存的id
        $emptyResultIds = array_diff($allIds, $hasResultIds);

        if ($this->debug) {
            echo 'redis中没有数据的id: ' . PHP_EOL;
            print_r($emptyResultIds);
        }

        // 如果部分结果不在redis中，需要查询数据库
        if (count($emptyResultIds) > 0) {
            $parsedResult = array_merge(
                $parsedResult,
                $this->requeryFromDatabaseForNotCachedIds($emptyResultIds)
            );
        }

        print_r($parsedResult);

        return $this->filterResultColumns($parsedResult);
    }

    /**
     * 从数据库中查询没有缓存的数据, 然后写入缓存
     *
     * @param $emptyResultIds
     * @return mixed
     */
    protected function requeryFromDatabaseForNotCachedIds($emptyResultIds)
    {
        return $this->resetWheresAndColumns($emptyResultIds, function () {
            return tap(
                parent::runSelect(),
                function ($result) {
                    $this->setCacheForEmptyIds($result);
                }
            );
        });
    }

    protected function resetWheresAndColumns($emptyResultIds, $callback = null)
    {
        // 查询所有的字段 然后存redis
        $this->columns = ['*'];

        $this->bindings['where'] = [];

        $this->wheres = [];

        $this->whereIn($this->getFullKeyName(), $emptyResultIds);

        $result = $callback();

        return $result;
    }

    /**
     * 缓存没有进行缓存的数据
     *
     * @param $resultForNotCachedIds
     */
    protected function setCacheForEmptyIds($resultForNotCachedIds)
    {
        $multiData = [];
        foreach ($resultForNotCachedIds as $rowObj) {
            $cacheKey = $this->getCacheKey($this->getPrimaryKeyValue($rowObj));
            $multiData[$cacheKey] = $this->createCacheDataRow($rowObj);
        }

        $this->cacher->put($multiData);

//        var_dump(Redis::mset($multiData));
    }

    /**
     * 根据存储方式, 生产对应的缓存数据格式
     *
     * @param $rowObj
     * @return array|false|string
     */
    protected function createCacheDataRow($rowObj)
    {
        if ($this->isCacheString()) {
            return json_encode($rowObj);
        }

        return (array) $rowObj;
    }

    /**
     * 解析缓存结果
     *
     * @param $row
     * @return mixed|object
     */
    protected function parseCacheResultRow($row)
    {
        if ($this->isCacheString()) {
            return json_decode($row);
        }

        return (object) $row;
    }


    /**
     * 获取模型缓存数据方式
     *
     * @return mixed
     * @throws UnsupportedCacheTypeException
     */
    protected function getCacheType()
    {
        $supportCacheType = ['string', 'hash'];

        $definedCacheType = $this->model->getCacheSaveType();

        if (!in_array($definedCacheType, $supportCacheType)) {
            throw new UnsupportedCacheTypeException(
                sprintf("The cache type {$definedCacheType} is not supported!")
            );
        }

        return $definedCacheType;
    }

    /**
     * 是否使用redis的string存储
     *
     * @return bool
     */
    protected function isCacheString()
    {
        return $this->getCacheType() == 'string';
    }

    /**
     * 是否使用redis的hash存储
     *
     * @return bool
     */
    protected function isCacheInHash()
    {
        return $this->getCacheType() == 'hash';
    }

    /**
     *
     *
     * @param $rowObj
     * @return mixed
     */
    protected function getPrimaryKeyValue($rowObj)
    {
        $primaryKey = $this->getKeyName();

        return $rowObj->$primaryKey;
    }

    /**
     * 获取缓存的键名
     * @param $value
     * @return string
     */
    protected function getCacheKey($value)
    {
        return str_replace(
            ['TABLE', 'VALUE'],
            [$this->getTableName(), $value],
            $this->model->getCacheKeyFormat()
        );
    }


    /**
     * 获取模型主键名
     *
     * @return string
     */
    public function getKeyName()
    {
        return $this->model->getKeyName();
    }

    /**
     * 获取模型表名
     *
     * @return string
     */
    public function getTableName()
    {
        return $this->model->getTable();
    }

    public function getCacheKeyFormat()
    {
        return $this->model->getCacheKeyFormat();
    }

    protected function createCacher()
    {
        $this->cacher = new CacherManager($this);
    }

    protected function getCacher()
    {
        if(is_null($this->cacher)) {
            $this->cacher = new CacherManager($this);
        }

        return $this->cacher;
    }

    public function insert(array $values)
    {
        if(parent::insert($values)) {
            $this->model->refresh();
        }
    }

    public function update(array $values)
    {
        $cacheKeys = $this->getKeysForUpdate() ?: $this->queryKeysForUpdate();

        if(parent::update($values)) {
            $this->getCacher()->del($cacheKeys);
        }
    }

    public function delete($id = null)
    {
        $cacheKeys = $this->getKeysForUpdate() ?: $this->queryKeysForUpdate();

        if(parent::delete($id)) {
            $this->cacher->del($cacheKeys);
        }
    }

    protected function getKeysForUpdate()
    {
        $whereColumns = array_map(function ($cloumn) {
            $segments = explode('.', $cloumn);
            return end( $segments );
        }, array_column($this->wheres, 'column'));

        $deletedColumn = $this->model->getDeletedAtColumn(); 

        if(isset($whereColumns[$deletedColumn])) {
            unset($whereColumns[$deletedColumn]);
        }

        if(in_array($deletedColumn, $whereColumns)) {
            $index = array_search($deletedColumn, $whereColumns);
            unset($whereColumns[$index]);
        }

        // 禁止直接删除更新所有的数据
        if(count($whereColumns) === 0) {
            throw new QueryConditionsNotFoundException( sprintf("The query %s has no conditions", $this->toSql()) );
        }
        
        if(count($whereColumns) > 1) {
            return [];
        }

        $qualifiedColumn = array_filter($this->wheres, function($where) use ($deletedColumn) {
            $segments = explode('.', $where['column']);
            $column = end( $segments );
            return $column != $deletedColumn;
        });

        $qualifiedColumn = $qualifiedColumn[0];

        if ($this->getKeyName() == $qualifiedColumn['column'] && 
            in_array($qualifiedColumn['operator'], ['in', '='])
        ) {
            return $qualifiedColumn['value'];
        }

        return [];
    }

    protected function queryKeysForUpdate()
    {
        [$allIds, $neededCacheKeys]= $this->buildCacheKeys(
            $this->queryPrimaryKeysFromDatabase()
        );

        if (count($neededCacheKeys) == 0) {
            return [];
        }

        return $neededCacheKeys;
    }

    protected function getAliasTable($column)
    {
        $segments = preg_split('/\s+as\s+/i', $this->from);

        $qualifiedColumn = end($segments).'.'.$column;
    }
}
